#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 该文件主要用于存放其他工具方法
import random
import pypinyin
import re
import string
import sys
import jsonpath
import paramiko

from commons.logger import log
from commons.assert_tools import Assert
from commons.config_tools import Cf
from commons.yaml_tools import Yl



class OtrTl:
    def str_split(self, s_str, delimiter, val_index=None, num=None):
        """
        功能:处理字符串按分隔符分割,从起始位置开始计数
        :s_str 要进行提取的字符串
        :delimiter  定义切割分隔符
        :val_index 从分割后的列表根据索引取某个值
        :num 按定义的分隔符规定切割次数
        :return 返回分割后的值
        """
        if val_index is None and num is None:  # 按分隔符分割字符串,分割后返回为列表
            split_result = s_str.split(delimiter)
            return split_result
        elif val_index is not None and num is None:  # 按分隔符分割字符串,返回分割后的列表,按列表切片取出具体值
            str_split = s_str.split(delimiter)
            split_result = str_split[val_index]
            return split_result
        elif val_index is not None and num is not None:  # 按分隔符分割字符串,指定分割次数,返回分割后的列表,按列表切片取出具体值
            str_split = s_str.split(delimiter, num)
            split_result = str_split[val_index]
            return split_result
        else:
            Assert().fail_assert("从起始位置开始计数的字符分割方法中没有该逻辑,请检查数据增加判断!")

    def str_rsplit(self, s_str, delimiter, val_index=None, num=None):
        """
        处理字符串按分隔符分割,从结尾位置开始计数
        :s_str 要进行提取的字符串
        :delimiter  定义切割分隔符
        :val_index 从分割后的列表根据索引取某个值
        :num 按定义的分隔符规定切割次数
        :return 返回分割后的值
        """
        if val_index is None and num is None:  # 按分隔符分割字符串,分割后返回为列表
            split_result = s_str.rsplit(delimiter)
            return split_result
        elif val_index is not None and num is None:  # 按分隔符分割字符串,返回分割后的列表,按列表切片取出具体值
            str_split = s_str.rsplit(delimiter)
            split_result = str_split[val_index]
            return split_result
        elif val_index is not None and num is not None:  # 按分隔符分割字符串,指定分割次数,返回分割后的列表,按列表切片取出具体值
            str_split = s_str.rsplit(delimiter, num)
            split_result = str_split[val_index]
            return split_result
        else:
            Assert().fail_assert("从结尾位置开始计数的字符分割方法中没有该逻辑,请检查数据增加判断!")

    def del_blank(self, s_str):
        """
        去除字符串里的空格
        s_str 字符串
        """
        new_str = s_str.replace(" ", "")
        return new_str

    def lst_in_order(self, nums_list):
        """
        功能:判断整数列表是否按顺序排序
        :nums_list 要判断的列表
        :return 是否排序的结果:如果整数列表被排序(从低到高的顺序),则返回True,否则返回False
        """
        sort_result = all(nums_list[i - 1] < nums_list[i] for i in range(1, len(nums_list)))
        return sort_result

    def get_index(self, val, lst=None, result=None, primary_field=None, get_index=0):
        """
        根据已知值获取该值在列表中的位置(索引)
        val 已知值
        lst 列表
        result 接口返回的结果
        primary_field  字段名称
        get_index 如果列表中有多个相同的值,取第几个
        """
        if lst is not None:  # 不带接口请求结果,在传入的列表中获取某个值在该列表中的位置(索引)
            if val in lst:
                index_s = [index for index, value in enumerate(lst) if value == val]  # 可能存在多个相同的值,将索引都取出来生成列表
                index = index_s[get_index]
                return index
            else:
                Assert().fail_assert("不带接口请求结果,值:%s,不在列表:%s中" % (str(val), str(lst)))
        else:  # 传接口请求结果,根据某个已知字段,获取该字段的值在返回列表中的位置(索引)
            lst = Assert(result).ext_val(primary_field)
            if len(lst) > 0:
                index = self.get_index(val, lst, get_index=get_index)
                return index
            else:
                Assert().fail_assert("带接口请求结果,值:%s,不在列表:%s中" % (str(val), str(lst)))

    def lst_sort(self, lst, sort_by=False, ):
        """
        对列表进行排序
        lst 列表
        sort_by 排序方式 ,reverse为True时是降序,False时是升序(默认)
        """
        # sort的参数reverse可以控制排序规则,revers为True时是降序,False时是升序(默认)
        new_lst = lst.sort(reverse=sort_by)
        return new_lst

    def disorder_lst(self, lst):
        """
        打乱列表排序,使列表无序
        lst 列表
        """
        return random.shuffle(lst)

    def get_dict_value(self, dict_data, dict_key):
        """
        获取字典里的值
        dict_data 字典
        dict_key  字典里对应的键
        """
        val = dict_data.get(dict_key)
        return val

    def lst_val_count(self, lst, val):
        """
        功能:计算列表中某个值的个数
        :list:列表
        :val:列表中的xx值
        :return:返回该值在列表中的个数
        """
        num = lst.count(val)
        log.info("值:%s,在列表:%s中的个数为:%s" % (str(val), str(lst), str(num)))
        return num

    def get_random_value(self, lst, num=1):
        """
        功能:用于从有序列表里取随机的值
        :lst:有序列表
        :num:取值个数 默认取一个
        :return:取出的值,返回的是列表
        """
        val = random.sample(lst, k=num)
        return val

    def find_str(self, pattern, str_s, flags=None):
        """
        在内容中匹配特定的值,函数返回包含所有匹配项的列表
        pattern:正则表达式的模式或模式字符串
        str_s:要搜索的字符串
        flags:可选参数,用于控制正则表达式的匹配方式,如是否区分大小写等
        """
        res = re.findall(pattern, str_s, flags)
        return res

    def lst_del_repeat(self, lst):
        """
        功能:用于从有序列表里去除重复值,且保持原有顺序不变
        :sequence:有序类型
        :return:返回去重后的列表"""
        nwe_list = []
        for i in lst:
            if i not in nwe_list:
                nwe_list.append(i)
        return nwe_list

    def lst_get_int(self, lst):
        """
        根据数组中的元素个数,输出从1开始连续的整数(个数取决于列表长度)
        lst 数组 如:lst=["w","q","e"]  得到的new_arr=[1,2,3]
        """
        new_arr = [i + 1 for i in range(len(lst))]
        return new_arr

    def in_lst_key_val(self, key, lst, is_not_in=False):
        """
        在数组中获取包含/不包含关键字的元素
        key 关键字,可模糊匹配
        lst 被查找的数组
        is_not_in  是否存在关键字
        如:lst = ["1qq", "2rr", "3aa"]  key="a"  得到的key_lst=["3aa"]
        """
        key_lst = []
        if is_not_in:
            for i in lst:
                if key not in i or key.upper() not in i:
                    key_lst.append(i)
        else:
            for i in lst:
                if key in i or key.upper() in i:
                    key_lst.append(i)
        return key_lst

    def join_pin_yin(self, value_list, key=None):
        """将数组中的元素转化为拼音,并根据需求取对应的中文"""
        value = []
        pinyin_lst = [''.join(pypinyin.lazy_pinyin(word)) for word in value_list]
        if key:
            for i in range(len(pinyin_lst)):
                if key in pinyin_lst[i]:
                    value.append(value_list[i])
            return value
        return pinyin_lst

    def random_str(self, length):
        """
        生成指定长度(包含字母大小写和数字)的随机字符串
        length  指定生成的字符长度
        """
        letters = string.ascii_letters + string.digits
        new_str = ''.join([random.choice(letters) for _ in range(length)])
        return new_str

    def get_pinyin(self, string, convert=0):
        """
        功能:中文转换成拼音,获取每个字的大写字母/小写字母,默认为大写
        :string 要进行转换成拼音的字符串
        :convert 是否转换成小写(0不转换 1转换)
        :return 返回每个字的大写字母/小写字母
        """
        result = pypinyin.pinyin(string, style=pypinyin.NORMAL)
        if convert == 0:
            convert_value = ''.join([i[0][0].upper() for i in result])
        elif convert == 1:
            convert_value = ''.join([i[0][0].lower() for i in result])
        else:
            convert_value = result
        log.info("中文:", string, "转换成拼音为:", convert_value)
        return convert_value

    def get_more_vaule(self, result, primary_field_lst, condition=0, index=0):
        """
        获取多个指定字段值  嵌套列表如[[],[]]
        result:接口返回的结果
        primary_field_lst:数组,要获取的字段名称
        condition:指定条件(0取字段列表里第1个值  1取字段所有值返回为列表)
        index:用于获取嵌套层中的每个list中的第index值 嵌套列表如[[],[]]
        return:返回最终处理后得到的值,返回为列表
        """
        vaule_list = []
        for i in primary_field_lst:
            if condition == 0:  # 列表时使用 如[1,2,3]
                vaule = Assert.ext_val(result, i)[0]
                vaule_list.append(vaule)
            elif condition == 1:  # 列表嵌套列表时使用 如列表lst=[[1,2,3],[4,5,6]]
                vaule_list_1 = []
                vaule1 = Assert.ext_val(result, i)
                vaule_list_1.append(vaule1)
                for j in range(len(vaule_list_1)):
                    value2 = vaule_list_1[j][index]
                    vaule_list.append(value2)
            else:
                Assert().fail_assert("获取多个字段值失败,请检查参数！")
        return vaule_list

    def judge_lst_vaule(self, value, lst):
        """
        判断列表里所有元素的值是否为同一个值
        vaule:需要判断的值
        lst:数组
        return:返回True False  (布尔值)
        """
        result = all(element == value for element in lst)
        return result

    def op_temp_param(self, param_name, param_value=None, op_type=0):
        """
        往临时参数文件(tempParam.yaml)里读或写数据
        param_name:参数名
        param_value:参数值
        op_type:操作类型,读还是写  0-写  1-读
        """
        if op_type == 0 and param_value is not None:
            Yl().modify_data(2, param_name, param_value)
            return param_value, param_value
        elif op_type == 1 and param_value is None:
            param_value = Yl().get_mod_all_val(2, param_name)
            return param_value
        else:
            Assert().fail_assert("临时参数写入失败,请检查!")

    def build_params(self, key1, key2, prams_list1, prams_list2, index=None, start=0):
        """
        构建2个参数的字典类型的请求参数
        key1, key2:构建字典的key(接口所需要的参数字段)
        prams_list1, prams_list2:字典需要的值
        """
        prams_list = []
        if isinstance(prams_list1, list) and isinstance(prams_list2, list):
            for i in range(len(prams_list1)):
                prams = {key1: prams_list1[i], key2: prams_list2[i]}
                prams_list.append(prams)
        elif isinstance(prams_list1, list):
            for i in prams_list1:
                prams = {key1: i, key2: prams_list2}
                prams_list.append(prams)
        else:
            prams = {key1: prams_list1, key2: prams_list2}
            prams_list.append(prams)
        if index:
            return prams_list[start:index]
        return prams_list

    def get_other_val(self, result, primary_field, known_val, others_field):
        """
        在嵌套列表中,根据一个已知字段的值,获取该组列表数据中的其他字段值
        result  接口返回的结果
        primary_field  已知值的字段名
        known_val  已知的值
        others_field  需要获取值的其他字段名(多个/单个)
        return 获取多个/单个字段的值
        """
        # 如 lst=[["id1","name1","status1"],["id2","name2","status2"]]
        # 取id1所对应的name1  [index]已知值在列表中的索引
        others_field_val = []
        index = self.get_index(known_val, result=result, primary_field=primary_field)  # 已知值在列表中的索引
        if isinstance(others_field, list):  # 获取多个字段的值
            for i in range(len(others_field)):
                field_val = Assert(result).ext_val(others_field[i])[index]  # 根据索引取其他字段的值
                others_field_val.append(field_val)
        else:  # 获取单个字段的值
            others_field_val = Assert(result).ext_val(others_field)[index]
        log.info("根据字段" + str(primary_field) + "的值获取到的其他字段的值为:" + str(others_field_val))
        return others_field_val

    def json_get_rela_val(self, result, primary_field, known_val=None):
        """
        在json中,根据一个已知字段的值,获取包含已知字段=已知值的完整数据
        result  接口返回的结果
        primary_field  已知值的字段名
        known_val  已知的值,列表,有多个值,不传时返回已知字段的所有结果
        """
        # 如 result={'list':[{"id":1,"name":11},{"id":2,"name":22}]}
        # 取id=1的完整数据:primary_field='id',known_val=[2],返回[{'id':2,'name':22}]
        if known_val and len(known_val) != 0:
            # 对接口返回进行过滤,查找所有包含primary_field字段的节点,提取字段的值等于或包含在known_val中的部分节点
            match = "$..*[?(@." + primary_field + " in " + str(known_val) + ")]"
        else:
            match = "$..[?(@." + primary_field + ")]"
        rela_val = jsonpath.jsonpath(result, match)
        return rela_val

    def extract_value(self, src_dict, key):
        """
        从嵌套字典中提取指定键的值,如果键不存在报错
        src_dict 字典
        key 需要获取值的键 key
        """
        if key in src_dict:
            return src_dict[key]
        for value in src_dict.values():
            if isinstance(value, dict):
                result = self.extract_value(value, key)
                if result is not None:
                    return result
        else:
            assert 0 == 1, "key不存在"

    def ran_str_off_lst(self, str_lib, len_range, lst, try_times=3):
        """
        从指定字符库,生成除特定字符串以外的,一定长度范围内的随机字符串
        str_lib  字符串,生成随机字符串的所有可用字符
        len_range  包含两个整数的列表,随机生成的字符串长度范围,如[1,2],即为生成字符串长度最小1位,最大2位
        lst  列表,包含不允许生成的字符串,大小写敏感
        try_times  整数,每种长度的字符串尝试生成的次数
        """
        ran_str = lst[0]
        min_len = len_range[0]
        max_len = len_range[1]
        k = 0
        a = int(k / try_times) + min_len

        # 若随机生成的字符串扔在指定范围内,且字符串长度未超出范围,即再次尝试
        while ran_str in lst and a <= max_len:
            ran_str = ''.join(random.choices(population=str_lib, k=a))
            k = k + 1
            a = int(k / try_times) + min_len

        if ran_str in lst:
            log.info("未成功生成不属于指定范围的字符串,最终得到的随机字符串为:" + ran_str)
            return None
        else:
            return ran_str

    def ssh_conn(self, str_command, host_n=None, user_n=None, u_pwd=None, pub_key=None, port=22, user="user"):
        """
        初始化SSH客户端,创建SSH连接
        str_command:要执行的命令字符串
        host_n:主机名或IP地址
        user_n:用户名
        u_pwd:密码(明文)
        pub_key:私钥文件对象,用于密钥认证
        port:端口号,默认22
        user:配置文件中的用户  (不传ip和用户信息时从配置读取)
        """
        if host_n is None and user_n is None and u_pwd is None:
            address = Cf().read_config_ini("envHost", "ugreen")
            host_n = OtrTl().str_split(OtrTl().str_split(address, "://", 1), ":", 0)  # 服务器ip
            user_n = Cf().read_config_ini(user, "user_name")  # ssh用户名
            u_pwd = Cf().read_config_ini(user, "user_pwd")  # ssh密码
        else:
            host_n = host_n
            user_n = user_n
            u_pwd = u_pwd
        pub_key = pub_key
        port = port
        try:
            # 创建ssh客户端实例
            client = paramiko.SSHClient()
            # 自动接受新的主机密钥,并将其添加到known_hosts文件中(有安全风险)
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            client.connect(host_n, port=port, username=user_n, password=u_pwd,
                           pkey=pub_key)
        except Exception as e:
            log.info("服务器连接失败,请检查信息!,连接失败原因:%s" % e)
            sys.exit()  # 如果报错使用sys的exit退出脚本
        # 在exec_command中一次执行多个命令,把要执行的命令之间用英文的分号";"分开 如:"dh f;pwd"
        stdin, stdout, stderr = client.exec_command(str_command)  # 打开一个Channel并执行命令
        output = stdout.read().decode()  # 获取标准输出
        error = stderr.read().decode()  # 获取错误输出
        if output:
            log.info("标准的输出结果为:\n%s" % output)  # 打印输出结果
        if error:
            Assert().fail_assert("错误信息为:\n%s" % error)
        client.close()
        return output
